<?php
if(!interface_exists('XDBDriver', false)){
	require(dirname(__FILE__).DIRECTORY_SEPARATOR.'XDBDriver.php');
}
if(!interface_exists('XDBTransaction', false)){
	require(dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'transaction/XDBTransaction.php');
}
if(!interface_exists('XDBXAResource', false)){
	require(dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'xa/XDBXAResource.php');
}
abstract class XAbstractDBDriver implements XDBDriver, XDBTransaction, XDBXAResource{
	protected $name = null;
	protected $extension = null;
	protected $config = array();
	protected $connection = null;
	protected $connected = false;
	protected $transacting = false;
	protected $xid = null;
	protected $host = null;
	protected $port = null;
	
	public function __construct($name, $config){
		$this->name = $name;
		if(!$this->extension
			|| !extension_loaded($this->extension)){
			throw new XORMException("Cannot load {$this->extension} extension. Please check your PHP configuration.");
		}
		$this->config = $config;
		if(!is_array($this->config)
			|| !isset($this->config['host'])
			|| !isset($this->config['username'])
			|| !isset($this->config['password'])
			|| !isset($this->config['database'])){
			throw new XORMException("Cannot load {$this->extension} configuration.");
		}
		register_shutdown_function(array($this, 'close'));
	}
	
	public function name(){
		return $this->name;
	}
	
	/**
	 * abstract functions, requires extending
	 */		

	public function connect(){}
	
	public function close(){}
	
	public function exec($command){}
	
	public function query($sql){}
	
	public function lastId(){}
	
	public function escape($str){}
	
	public function offsetKeyword(){}
	
	public function sizeKeyword(){}
	
	public function error(){}
	
	public function hasError(){}	
	
	public function version(){}
	
	/**
	 * generic database operations
	 */	
		
	/**
	 * load given table info from database
	 * @return XTable
	 */		
	public function loadTable($tableName){
		$table = new XBasicTable();
		$table->setName($tableName);
		$columns = array();
		$results = $this->query("SHOW COLUMNS FROM `{$tableName}`;");
		foreach($results as $result){
			$columns[$result['Field']] = $result;
			if(isset($result['Key']) && $result['Key']=='PRI'){
				$table->setPK($result['Field']);
			}
		}
		$table->setColumns($columns);
		return $table;
	}
	
	public function insert(XTable $table, XModel $model){
		$attrs = array();
		foreach($table->columns() as $key => $column){
			$value = $model->get($key);
			if(is_null($value)){
				continue;
			}
			$attrs["`{$key}`"] = "'".$this->escape($value)."'";
		}
		if(count($attrs) == 0){
			throw new XORMException("the model ".get_class($model)." of table {$table->name()} is empty.");
		}
		$keys = implode(" , ", array_keys($attrs));
		$values = implode(" , ", array_values($attrs));
		$sql = "insert into {$table->name()} ( $keys ) values( $values )";
		$result = $this->exec($sql);
		return $result > 0;
	}
	
	public function update(XTable $table, XModel $model){
		$pk = $model->get($table->PK());
		if(!$pk){
			throw new XORMException("the primary key of model ".get_class($model)." is null.");
		}
		$updates = array();		
		foreach($table->columns() as $key => $column){
			if($key == $table->PK()){
				continue;
			}
			$value = $model->get($key);
			if($value == NULL){
				continue;
			}
			$updates[]= "`{$key}` = '{$this->escape($value)}'";
		}
		if(count($updates) == 0){
			throw new XORMException("the model ".get_class($model)." of table {$table->name()} is empty.");
		}		
		$sql = "update {$table->name()} set ".implode(" , ", $updates)." where {$table->PK()}={$pk}";
		$result = $this->exec($sql);
		return $result > 0;
	}
	
	public function remove(XTable $table, XModel $model){
		$pk = $model->get($table->PK());
		if(!$pk){
			throw new XORMException("the primary key of model ".get_class($model)." is null.");
		}
		$sql = "delete from {$table->name()} where {$table->PK()}={$pk}";
		$result = $this->exec($sql);
		return $result > 0;
	}
		
	public function queryCriteria(XTable $table, XCriteria $criteria){
		$translator = new XSQLCriteriaTranslator();
		$translator->setDriver($this);
		return $this->query($translator->translate($table, $criteria));
	}
		
	/**
	 * generic local transactions
	 */	
	
	public function beginTransaction(){
		if($this->transacting){			
			return true;
		}
		$this->exec("START TRANSACTION;");
		if($this->hasError()){	
			$this->transacting = false;
			return false;
		}
		$this->transacting = true;
		return true;
	}
	
	public function commit(){
		if(!$this->transacting){			
			return false;
		}
		$this->exec("COMMIT;");
		if($this->hasError()){			
			return false;
		}
		$this->transacting = false;
		return true;
	}

	public function rollback(){
		if(!$this->transacting){			
			return false;
		}
		$this->exec("ROLLBACK;");
		if($this->hasError()){
			return false;
		}
		$this->transacting = false;
		return true;
	}
	
	public function isTransacting(){
		return $this->transacting;
	}
	
	/**
	 * generic xa transactions
	 */
			
	public function commitXA($xid, $onePhase = null){
		$this->xid = null;
		return $this->exec("XA COMMIT '{$xid}';");
	}
	public function endXA($xid, $flags = null){
		return $this->exec("XA END '{$xid}';");
	}
	public function prepareXA($xid){
		return $this->exec("XA PREPARE '{$xid}';");
	}
	public function recoverXA($flag = null){
		return $this->exec("XA RECOVER;");
	}
	public function rollbackXA($xid){
		$this->xid = null;
		return $this->exec("XA ROLLBACK '{$xid}';");
	}
	public function startXA($xid, $flags = null){
		if($this->xaStarted()){
			return true;
		}
		$this->xid = $xid;
		return $this->exec("XA START '{$xid}';");
	}
	public function supportsXA(){
		if(!isset($this->config['xa'])
			|| $this->config['xa'] !== true){
			return false;
		}
		return $this->versionSupportsXA();
	}
	protected function versionSupportsXA(){
		return false;
	}
	public function xaStarted(){
		return !is_null($this->xid);
	}
	public function rmUID(){
		return get_class($this)."://".$this->host.':'.$this->port;
	}
}
?>